package cn.maihe.elg.operation.centers.fuzhou.handler;

import cn.maihe.elg.operation.centers.fuzhou.auth.FZCenterAuthConfig;
import cn.maihe.elg.operation.centers.fuzhou.auth.FZHeaderInfo;
import cn.maihe.elg.operation.centers.fuzhou.dto.FZBaseRespDTO;
import cn.maihe.elg.operation.centers.fuzhou.dto.handler.FZCheckReqDTO;
import cn.maihe.elg.operation.centers.fuzhou.enums.HyMakeRespCodeEnum;
import cn.maihe.elg.operation.centers.fuzhou.exception.HyMakeException;
import cn.maihe.elg.operation.centers.supports.annotation.CenterTradeService;
import cn.maihe.elg.operation.exception.CenterException;
import cn.maihe.elg.operation.exception.ExceptionUtils;
import cn.maihe.elg.operation.model.enums.CenterNoEnum;
import cn.maihe.elg.operation.model.enums.CenterProviderEnum;
import cn.maihe.elg.operation.utils.DateUtil;
import cn.maihe.elg.operation.utils.ElgLogger;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.PropertyNamingStrategy;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
 * @Description
 * @Author caixh
 * @Date 2022/3/30.
 */
@RestController
@Slf4j
public class FZCenterHandler {
    public static final CenterProviderEnum centerProvider = CenterProviderEnum.ESB_FUZHOU;

    /**
     * 中心服务商的接口实现类
     */
    private final Map<String, FZCenterTradeService> tradeServiceMap = new HashMap<>();
    /**
     * 能够使用与该中心服务商的 中心信息
     */
    public static final List<String> enableCenterNos = Lists.newArrayList(CenterNoEnum.FUZHOU.getCenterNo());

    public FZCenterHandler(@SuppressWarnings("rawtypes") final Collection<FZCenterTradeService> baseCenterServices) {
        super();
        baseCenterServices.forEach(item -> {
            CenterTradeService ts = AnnotationUtils.findAnnotation(item.getClass(), CenterTradeService.class);
            if (centerProvider == ts.provider()) {
                tradeServiceMap.put(ts.tradeType().name().toLowerCase(), item);
            }
        });
    }


    /**
     * 福州中心 http://ip:port/xxx/{tradeType}
     */
    @Profile(value = {"dev", "test"})
    @PostMapping(value = "/test/v1/center/{centerNo}/{orgCode}/{tradeType}",
            consumes = {"application/json", "application/xml", "application/x-www-form-urlencoded", "multipart/form-data"})
    public @ResponseBody
    Object testMockService(@PathVariable("centerNo") String centerNo, @PathVariable("orgCode") String orgCode, @PathVariable("tradeType") final String tradeType,
                           @RequestHeader(required = false) Map<String, Object> headerMap, @RequestBody(required = false) String clearBizReqJsonBody, @ModelAttribute FZCheckReqDTO fzCheckReqDTO,
                           final HttpServletRequest req, final HttpServletResponse resp) {
        DeferredResult<Object> dr = new DeferredResult<Object>();
        if (!this.tradeServiceMap.containsKey(tradeType.toLowerCase())) {
            dr.setResult(buildRespException(HyMakeRespCodeEnum.FAILURE, "不支持的交易类型"));
            return dr;
        }
        CenterNoEnum centerNoEnum;
        try {
            centerNoEnum = CenterNoEnum.resolve(centerNo);
        } catch (Exception e) {
            log.error("暂不支持的交易中心[{}]:{}", centerNo, e.getMessage(), e);
            dr.setResult(buildRespException(HyMakeRespCodeEnum.FAILURE, "暂不支持的交易中心[" + centerNo + "]"));
            return dr;
        }
        FZCenterTradeService FZCenterTradeService = this.tradeServiceMap.get(tradeType.toLowerCase());
        FZCenterAuthConfig centerAuthConfig = FZCenterTradeService.getAuthConfig(centerNoEnum);

        if (req.getContentType().contains("application/x-www-form-urlencoded")) {
            clearBizReqJsonBody = JSON.toJSONString(fzCheckReqDTO);
        } else if (req.getContentType().contains("multipart/form-data")) {
            clearBizReqJsonBody = JSON.toJSONString(fzCheckReqDTO);
        }

        FZHeaderInfo headerInfo = new FZHeaderInfo();
        //加签业务数据
        String sign = centerAuthConfig.getFZAuthorization().signature(clearBizReqJsonBody);
        JSONObject clearBizReqJson = JSON.parseObject(clearBizReqJsonBody);
        clearBizReqJson.put("sign", sign);
        String clearBizReqJsonStr = JSON.toJSONString(clearBizReqJson);
        // 加密业务数据
        String encryptAndB64ReqBody = centerAuthConfig.getFZEncryption().encrypt(clearBizReqJsonStr);
        if (req.getContentType().contains("application/x-www-form-urlencoded")) {
            fzCheckReqDTO.setParam(encryptAndB64ReqBody);
        } else if (req.getContentType().contains("multipart/form-data")) {
            fzCheckReqDTO.setParam(encryptAndB64ReqBody);
        }
//        String encryptAndB64ReqBody = clearBizReqJsonBody;
//        try {
//            headerInfo = new FZHeaderInfo();
//            headerInfo.setVersion(centerAuthConfig.getCenterInfo().getVersion());
//            headerInfo.setAppid(centerAuthConfig.getCenterInfo().getVersion());
//            headerInfo.setRequestid(IdGenerate.getIdStr());
//            headerInfo.setTimestamp(String.valueOf(System.currentTimeMillis()));
        //明文业务数据签名

//            headerInfo.setSign(centerAuthConfig.getFZAuthorization().signature(clearBizReqJsonBody));
//        } catch (Exception e) {
//            log.error("请求头参数错误", e.getMessage(), e);
//            dr.setResult(buildRespException(HyMakeRespCodeEnum.FAILURE, "请求头参数错误[" + e.getMessage() + "]"));
//            return dr;
//        }

        Map<String, Object> mockHeadMap = BeanMap.create(headerInfo);

        DeferredResult deferredResult = this.doService(centerNo, orgCode, tradeType, mockHeadMap, req, resp, encryptAndB64ReqBody, fzCheckReqDTO.getGuarantee_pdf());
        deferredResult.setResultHandler(r ->

        {
            //解密还原不重新计算签名
            Object respData = deferredResult.getResult();
            if (respData != null && respData instanceof String) {
                FZBaseRespDTO respDTO = JSON.parseObject((String) respData, FZBaseRespDTO.class);
                Object bizRespData = respDTO.getData();
                if (bizRespData instanceof String && bizRespData != null) {
//                    String clearBizData = centerAuthConfig.getFZEncryption().decrypt((String) bizRespData);
                    String clearBizData = (String) bizRespData;
                    JSONObject jsonObject = JSON.parseObject(clearBizData);
                    respDTO.setData(jsonObject);
                }
                dr.setResult(respDTO);
                log.info("测试还原数据结果：{}", JSON.toJSONString(respDTO));
            }
        });

        return dr;
    }

    /**
     * 福州中心 http://ip:port/xxx/{tradeType}
     */
    @PostMapping(value = "/v1/center/{centerNo}/{orgCode}/{tradeType}",
            consumes = {"application/json", "application/xml", "application/x-www-form-urlencoded", "multipart/form-data"})
    public @ResponseBody
    DeferredResult<Object> service(@PathVariable("centerNo") String centerNo, @PathVariable("orgCode") String orgCode, @PathVariable("tradeType") final String tradeType,
                                   @RequestHeader(required = false) Map<String, Object> headerMap, @RequestBody(required = false) String encryptReqBody, @ModelAttribute FZCheckReqDTO fzCheckReqDTO,
                                   final HttpServletRequest req, final HttpServletResponse resp) {
        if (req.getContentType().contains("application/x-www-form-urlencoded")) {
            encryptReqBody = fzCheckReqDTO.getParam();
        } else if (req.getContentType().contains("multipart/form-data")) {
            encryptReqBody = fzCheckReqDTO.getParam();
        }
        return this.doService(centerNo, orgCode, tradeType, headerMap, req, resp, encryptReqBody, fzCheckReqDTO.getGuarantee_pdf());
    }


    @SuppressWarnings("unchecked")
    protected DeferredResult<Object> doService(String centerNo, String orgCode, String tradeType, Map<String, Object> headerMap, final HttpServletRequest req, final HttpServletResponse resp, final String encryptReqBody, MultipartFile file) {

        DeferredResult<Object> dr = new DeferredResult<Object>();
        if (!this.tradeServiceMap.containsKey(tradeType.toLowerCase())) {
            dr.setResult(buildRespException(HyMakeRespCodeEnum.FAILURE, "不支持的交易类型"));
            return dr;
        }
        CenterNoEnum centerNoEnum;
        try {
            centerNoEnum = CenterNoEnum.resolve(centerNo);
        } catch (Exception e) {
            log.error("暂不支持的交易中心[{}]:{}", centerNo, e.getMessage(), e);
            dr.setResult(buildRespException(HyMakeRespCodeEnum.FAILURE, "暂不支持的交易中心[" + centerNo + "]"));
            return dr;
        }
        log.info("请求头部参数:{}", JSON.toJSONString(headerMap));
        log.info("请求报文参数[明文加密字符串]:{}", JSON.toJSONString(encryptReqBody));

//        FZHeaderInfo headerInfo;
//        try {
//            headerInfo = FZHeaderInfo.of(headerMap);
//        } catch (Exception e) {
//            log.error("请求头参数错误", e.getMessage(), e);
//            dr.setResult(buildRespException(HyMakeRespCodeEnum.FAILURE, "请求头参数错误[" + e.getMessage() + "]"));
//            return dr;
//        }

        FZCenterTradeService FZCenterTradeService = this.tradeServiceMap.get(tradeType.toLowerCase());
        FZCenterAuthConfig FZCenterAuthConfig = FZCenterTradeService.getAuthConfig(centerNoEnum);
        Map<String, Object> attrMap = new HashMap<>(2);
        attrMap.put("guarantee_pdf", file);

        CompletableFuture
                .supplyAsync(() -> {
                    try {
                        //解密&验签后获取bizReqBody
                        Map<String, Object> clearParams = this.decryptAndVerifySignature(encryptReqBody, FZCenterAuthConfig);
                        return FZCenterTradeService.doService(centerNoEnum, orgCode, clearParams, attrMap);
                    } catch (Exception e) {
                        throw buildHyMakeException(e);
                    }
                })
                .whenCompleteAsync((baseResp, t) -> {
                    if (t != null) {
                        HyMakeException te = buildHyMakeException(t);
                        log.error("中心：{},交易类型：{},服务错误：{}", centerNoEnum.getCenterName(), tradeType, t.getMessage(), t);
                        baseResp = buildRespException(te.getStatusEnum(), te.getMessage());
                    }

                    //加密加签操作
//                    String encryptAllResp = this.encryptAndSignature(headerInfo, resp, baseResp, FZCenterAuthConfig, centerNoEnum, tradeType);
                    String encryptAllResp = JSON.toJSONString(baseResp);
                    dr.setResult(encryptAllResp);
                    log.info("中心：{},交易类型：{},响应报文：{}", centerNoEnum.getCenterName(), tradeType, dr.getResult());
                });
        return dr;
    }

    private String encryptAndSignature(FZHeaderInfo reqHeader, HttpServletResponse response, FZBaseRespDTO baseResp, FZCenterAuthConfig FZCenterAuthConfig, CenterNoEnum centerNoEnum, String tradeType) {
        log.info("中心：{},交易类型：{},响应报文明文：{}", centerNoEnum.getCenterName(), tradeType, JSON.toJSONString(baseResp));
        Object data = baseResp.getData();
        //响应业务数据加密
//        if (data != null) {
//            String clearBizRespData = JSON.toJSONString(data);
//            String encryptBizRespData = hyMakeCenterAuthConfig.getHyMakeEncryption().encrypt(clearBizRespData);
////            String encryptBizRespData = clearBizRespData;
//            baseResp.setData(encryptBizRespData);
//        }

        //整个报文签名
        FZHeaderInfo respHead = new FZHeaderInfo();
        respHead.setVersion(FZCenterAuthConfig.getCenterInfo().getVersion());
        respHead.setAppid(FZCenterAuthConfig.getCenterInfo().getAppId());
//        respHead.setSign();
        respHead.setRequestid(reqHeader.getRequestid());
        respHead.setTimestamp(DateUtil.getFulltime());

        String allRespJson = JSON.toJSONString(baseResp);
        String signature = FZCenterAuthConfig.getFZAuthorization().signature(allRespJson);
        respHead.setSign(signature);

        BeanMap.create(respHead).forEach((k, v) -> response.setHeader((String) k, (String) v));

        String clearAllRespJson = JSON.toJSONString(baseResp);
        log.info("中心：{},交易类型：{},响应报文头：{},响应报文密文：{}", centerNoEnum.getCenterName(), tradeType, JSON.toJSONString(respHead), clearAllRespJson);

//        String encryptAllResp = hyMakeCenterAuthConfig.getHyMakeEncryption().encrypt(clearAllRespJson);

        return clearAllRespJson;
    }

    /**
     * 解密验签及转换param
     *
     * @param encryptAndB64ReqBody
     * @param FZCenterAuthConfig
     * @return
     */
    private Map<String, Object> decryptAndVerifySignature(String encryptAndB64ReqBody, FZCenterAuthConfig FZCenterAuthConfig) {
        /**
         * 业务数据明文加密后字符串
         */
        Assert.hasText(encryptAndB64ReqBody, "请求报文为空");


        // 解密、验签及反序列化
        String clearBizReqJsonBody;
        try {
            clearBizReqJsonBody = FZCenterAuthConfig.getFZEncryption().decrypt(encryptAndB64ReqBody);
            //body部分明文传输
//            clearBizReqJsonBody = encryptAndB64ReqBody;
            ElgLogger.info(log, log -> log.info("解密完请求报文:{}", clearBizReqJsonBody));
        } catch (Exception e) {
            throw HyMakeException.of(HyMakeRespCodeEnum.FAILURE, "报文解密失败:" + e.getMessage(), e);
        }

        try {
            // 验签
            boolean verifyTF = this.verifySignature(FZCenterAuthConfig, clearBizReqJsonBody);
            if (!verifyTF) {
                throw HyMakeException.of(HyMakeRespCodeEnum.FAILURE, "验签失败");
            }
        } catch (Exception e) {
            throw HyMakeException.of(HyMakeRespCodeEnum.FAILURE, "验签异常:" + e.getMessage(), e);
        }

        Map<String, Object> params = JSON.parseObject(clearBizReqJsonBody, Map.class);

        return params;
    }

    public HyMakeException buildHyMakeException(CenterException ce) {
        HyMakeRespCodeEnum respCodeEnum = HyMakeRespCodeEnum.FAILURE;
        //待优化明细
        if (ce.getInnerRespCode().isFailure()) {
            respCodeEnum = HyMakeRespCodeEnum.FAILURE;
        } else if (ce.getInnerRespCode().isException()) {
            respCodeEnum = HyMakeRespCodeEnum.FAILURE;
        } else if (ce.getInnerRespCode().isTimeOut()) {
            respCodeEnum = HyMakeRespCodeEnum.FAILURE;
        }
        return HyMakeException.of(respCodeEnum, ce.getMessage());
    }


    protected boolean verifySignature(FZCenterAuthConfig authConfig, String clearBizReqJsonBody) {
        try {
            return authConfig.getFZAuthorization().verifySignature(clearBizReqJsonBody);
        } catch (Exception e) {
            throw HyMakeException.error("验签失败:" + e.getMessage(), e);
        }
    }

    private FZBaseRespDTO buildRespException(HyMakeRespCodeEnum statusEnum, String msg) {
        return FZBaseRespDTO.of(statusEnum, msg);
    }

    private HyMakeException buildHyMakeException(Throwable t) {
        HyMakeException te = ExceptionUtils.isAssignableFrom(HyMakeException.class, t);
        if (te == null) {
            CenterException ce = ExceptionUtils.isAssignableFrom(CenterException.class, t);
            if (ce != null) {
                te = buildHyMakeException(ce);
            } else {
                SocketTimeoutException timeout = ExceptionUtils.isAssignableFrom(SocketTimeoutException.class, t);
                if (timeout != null) {
                    te = HyMakeException.of(HyMakeRespCodeEnum.FAILURE, "请求超时", t);
                } else {
                    te = HyMakeException.error("请求错误:" + t.getMessage(), t);
                }
            }
        }
        return te;
    }

}
